
#include <xbi/xbiimage.h>

#include <stdint.h>
#include <string.h>

#if 0
#pragma mark Definitions
#endif

#define      _XBINumberOfKnowFileSystems 3
static off_t _XBIKnowFileSystemOffsets[_XBINumberOfKnowFileSystems] = { 0, 0xFD90000, 0x18300000 };

#define         _XBIXboxFSMagicCookieOffset  0x10000 // Offset of the magic cookie from the beginning of the file system
#define         _XBIXboxFSMagicCookieLength  20
static char * _XBIXboxFSMagicCookie = "MICROSOFT*XBOX*MEDIA";

#define         _XBIVideoHeaderSize kXBIDVDSectorSize
#define         _XBIVideoHeaderMagicCookieLength 8
static byte_t _XBIVideoHeaderMagicCookie[_XBIVideoHeaderMagicCookieLength] = { 0x01, 0x43, 0x44, 0x30, 0x30, 0x31, 0x01, 0x00 }; // " CD001 \0"

#define _XBIFSRootSectorSize kXBIDVDSectorSize

#define _XBIFileCopyBufferSize 32<<20 // 32 MB

static char * _XBIXboxDefaultExecutablePath = "/default.xex";
static size_t _XBIXboxDefaultExecutablePathLength = 12;

struct _XBIImage {
    FILE * file;
    off_t  fileSize;
    
    off_t  dataPartitionOffset;
    
    off_t  fileSystemRootOffest;
    
    XBISecuritySector * SS;
    XBIPhysicalFormatInformation * PFI;
    XBIDiskManufacturingInformation * DMI;
    /*
     This DMI is used when the disc hasn't been unlocked by the Xbox' DVD drive firmware
     Once the disk has been unlocked, the SS' DMI is used instead
     Therefore, the image native DMI (image->DMI) points to the video partition
     On the other hand, image->SS->dmi points to the actual game data
     */
    XBIXboxEXecutable * defaultXEX;
    
    byte_t hasVideoL0;
    byte_t hasVideoL1OnL0;
    byte_t hasVideoL1OnL1;
    
    XBIIntroVideoHalf * firstIVH;
    XBIIntroVideoHalf * secondIVH;
    
    XBIXFSElement * fileSystemRoot;
};

#if 0
#pragma mark Private functions
#endif

static off_t _XBIImageGetVidL0Size(XBIImage * image) {
    return XBIPFIGetLayer0SizeInSectors(image->PFI)*kXBIDVDSectorSize; // image->PFI is the 'unlocked' PFI, therefore the PFI of a 'video' disc
}

static off_t _XBIImageGetVidL1Size(XBIImage * image) {
    return XBIPFIGetLayer1SizeInSectors(image->PFI)*kXBIDVDSectorSize; // image->PFI is the 'unlocked' PFI, therefore the PFI of a 'video' disc    
}

static off_t _XBIImageGetVidL0Position(XBIImage * image) {
    return 0;
}

static off_t _XBIImageGetVidL1Position(XBIImage * image) {
    off_t vidL0Length;
    off_t dataSize;
    off_t padding;
    
    if (image->PFI == NULL || image->SS == NULL || XBISSGetPFI(image->SS) == NULL)
        return -1;
    
    vidL0Length = XBIPFIGetLayer0SizeInSectors(image->PFI)*kXBIDVDSectorSize;
    dataSize = XBIPFIGetDiscSize(XBISSGetPFI(image->SS));
    padding = (XBIPFIGetStartPSN(XBISSGetPFI(image->SS)) - XBIPFIGetStartPSN(image->PFI)) * kXBIDVDSectorSize * 2;
    padding -= vidL0Length;
    
    // first data sector
    // off_t startSector = image->SS->pfi->startPSN * kXBIDVDSectorSize;
    
    return dataSize + padding;
}

static off_t _XBIImageISOSizeWithSplitVid(XBIImage * image) {
    off_t pad;
    if (image->SS != NULL && image->PFI != NULL) {
        pad = XBISSGetDataPartitionStart(image->SS) - _XBIImageGetVidL0Size(image);
        return XBISSGetDataPartitionStart(image->SS) + XBISSGetDiscSize(image->SS) + _XBIImageGetVidL1Size(image) + pad;
    }
    return -1;
}

static int _XBIImageCheckDateLink(XBISecuritySector * ss, XBIDiskManufacturingInformation * dmi) {
    int returnValue = (XBISSGetAuthoringDate(ss) == XBIDMIGetMasteringDate(dmi));
    if (returnValue)
        XBILog("DMI and SS dates match\n");
    else
        XBILog("DMI and SS dates DO NOT match\n");
    return returnValue;
}

static int _XBIImageCheckMediaIDLink(XBISecuritySector * ss, XBIDiskManufacturingInformation * dmi, XBIXboxEXecutable * xex) {
    int returnValue = 0;
    if (ss == NULL || dmi == NULL || xex == NULL)
        return 0;
    returnValue = (memcmp(XBISSGetMediaID(ss), XBIDMIGetMediaID(dmi), _XBIMediaIDLength) == 0);
    if (returnValue) {
        XBILog("SS and DMI media ID match\n");
        returnValue = (memcmp(XBIDMIGetMediaID(dmi), XBIXEXGetMediaID(xex), _XBIMediaIDLength) == 0);
        if (returnValue) {
            XBILog("DMI and XEX media ID match\n");            
        } else {
            XBILog("DMI and XEX media ID DO NOT match !\n");
        }
    } else {
        XBILog("SS and DMI media ID DO NOT match !\n");
    }
    return returnValue;
}

static int _XBIImageStealthCheckLevel2(XBIImage * image) {
    XBIError anError;
    if (image == NULL)
        return 0;
    
    // TODO: Work here (add extra checks)
    return (_XBIImageCheckDateLink(XBIImageGetSS(image), XBIImageGetDMI(image)) && _XBIImageCheckMediaIDLink(XBIImageGetSS(image), XBIImageGetDMI(image), XBIImageGetDefaultXEX(image, &anError)));
}

static int _XBIImageWriteBufferAtOffset(XBIImage * image, off_t offset, byte_t * buffer, size_t bufferSize, XBIError * error) {
    if (image == NULL || image->file == NULL)
        goto error;
    
    if (fseeko(image->file, offset, SEEK_SET) != 0) {
        *error = kXBIFileIOError;
        goto error;
    }
    
    if (fwrite(buffer, bufferSize, 1, image->file) != 1) {
        *error = kXBIFileIOError;
        goto error;
    }
    
    return 0;    
    
error:
    return -1;
}

static int _XBIImageStealthCheckLevel1(XBIImage * image) {
    if (image == NULL)
        return 0;
    
    XBILog("Level 1 stealth check (file integrity)\n");
    
    if (image->dataPartitionOffset <= 0) { // Check for video partition
        XBILog("ISO has no video partition!\n");
        return 0;
    }
    
    if ((image->SS == NULL) || (image->PFI == NULL) || (image->DMI == NULL)) {
        XBILog("ISO is missing one or more stealth files\n");
        return 0;
    }
    
    if (image->hasVideoL0 == 0 || (image->hasVideoL1OnL0 == 0 && image->hasVideoL1OnL1 == 0)) {
        XBILog("ISO has bad video\n");
        return 0;
    }
    
    // TODO: Check SS for integrity
    
    return 1;
}

#if 0
#pragma mark Creation - Copy - Deletion
#endif

XBIImage * XBIImageCreateFromFile(FILE * file, XBIError * error) {
    int i;
    byte_t * magicCookieBuffer = NULL;
    byte_t * stealthBuffer = NULL;
    byte_t * videoBuffer = NULL;
    XBIImage * newImage = NULL;
    byte_t * rootSectorBuffer = NULL;
    int32_t rootSectorBlockOffset = 0;
    int32_t rootSectorSize = 0;
    int32_t videoSizeInSectors = 0;    
    byte_t * videoL0Buffer = NULL;
    byte_t * videoL1Buffer = NULL;
    
    // Sanity checks of the system
    XBIAssert(sizeof(off_t) >= 6, "File sizes potentially bigger than 8 Gb\n");
    
    if (file == NULL)
        goto error;
    
    newImage = malloc(sizeof(XBIImage));    
    if (newImage == NULL) {
        *error = kXBINotEnoughMemory;
        goto error;
    }
    memset(newImage, 0, sizeof(XBIImage));

    
    newImage->file = file;
    newImage->fileSize = XBIGetFileSize(file);
    if (newImage->fileSize < 0) {
        XBILog("Couldn't get file size\n");
        *error = kXBIFileIOError;
        goto error;
    }
    
    if (newImage->fileSize < 65536) { // 64 Kilobytes
        // File is way too small
        XBILog("File is WAY too small, or empty\n");
        *error = kXBIFileTooSmall;
        goto error;
    }
    
    if (newImage->fileSize == 1073741824) { // 1 Gigabyte
        XBILog("Cannot load split format ISO files!\n");
        *error = kXBIFileWrongFormat;
        goto error;
    }
    
    XBILog("Image size is %lld\n", (long long)newImage->fileSize);
    
    XBILog("Searching Xbox filesystem offset\n");
    newImage->dataPartitionOffset = 0;    
    
    magicCookieBuffer = malloc(_XBIXboxFSMagicCookieLength);
    if (magicCookieBuffer == NULL) {
        *error = kXBINotEnoughMemory;
        goto error;
    }
    
    for (i = 0; i < _XBINumberOfKnowFileSystems ; i++) {
        off_t currentOffset = _XBIKnowFileSystemOffsets[i];
        
        XBILog("Trying offset 0x%qX\n", (long long)currentOffset);
        
        if (newImage->fileSize > currentOffset + _XBIXboxFSMagicCookieOffset + _XBIXboxFSMagicCookieLength) {
            if (fseeko(newImage->file, currentOffset + _XBIXboxFSMagicCookieOffset, SEEK_SET) != 0) {
                *error = kXBIFileIOError;
                goto error;
            }
            
            if (fread(magicCookieBuffer, _XBIXboxFSMagicCookieLength, 1, newImage->file) != 1) {
                *error = kXBIFileIOError;
                goto error;
            }
            
            if (memcmp(magicCookieBuffer, _XBIXboxFSMagicCookie, _XBIXboxFSMagicCookieLength) == 0) {
                // Magic cookie found !
                XBILog("Magic cookie (%s) found, at offset 0x%qX\n", _XBIXboxFSMagicCookie, (long long)currentOffset);
                newImage->dataPartitionOffset = currentOffset;
                break;
            }
        }
    }
    free(magicCookieBuffer);
    magicCookieBuffer = NULL;
        
    if (newImage->dataPartitionOffset == 0) {
        // We couldn't find a magic cookie
        XBILog("Magic cookie not found ! Stealth files unavailable\n");
        *error = kXBIFileWrongFormat;
        goto error;
    }
    
    XBILog("File system offset is 0x%qX\n", (long long)newImage->dataPartitionOffset);
    
    stealthBuffer = malloc(_XBISecuritySectorSize + _XBIPhysicalFormatInformationSize + _XBIDiskManufacturingInformationSize);
    
    if (stealthBuffer == NULL) {
        *error = kXBINotEnoughMemory;
        goto error;    
    }
    
    XBILog("Reading stealth file data\n");
    
    if (fseeko(newImage->file, newImage->dataPartitionOffset - (_XBISecuritySectorSize + _XBIPhysicalFormatInformationSize + _XBIDiskManufacturingInformationSize) , SEEK_SET) != 0) {
        *error = kXBIFileIOError;
        goto error;
    }
    
    if (fread(stealthBuffer, _XBISecuritySectorSize + _XBIPhysicalFormatInformationSize + _XBIDiskManufacturingInformationSize, 1, newImage->file) != 1) {
        *error = kXBIFileIOError;
        goto error;
    }
    
    // Memory layout : starting at fsOffset - 6144 : PFI, DMI, SS
    newImage->PFI = XBIPFICreateFromBuffer(kXBIPFIXbox360Version, stealthBuffer, _XBIPhysicalFormatInformationSize, error);
    newImage->DMI = XBIDMICreateFromBuffer(stealthBuffer + _XBIPhysicalFormatInformationSize, _XBIDiskManufacturingInformationSize, error);
    newImage->SS = XBISSCreateFromBuffer(stealthBuffer + _XBIPhysicalFormatInformationSize + _XBIDiskManufacturingInformationSize, _XBISecuritySectorSize, error);
    
    free(stealthBuffer);
    stealthBuffer = NULL;
    
    XBILog("Reading FileSystem\n");
    
    rootSectorBuffer = malloc(_XBIFSRootSectorSize);
    if (rootSectorBuffer == NULL) {
        *error = kXBINotEnoughMemory;
        goto error;
    }
    
    if (fseeko(newImage->file, newImage->dataPartitionOffset + 32 * kXBIDVDSectorSize, SEEK_SET) != 0) {
        *error = kXBIFileIOError;
        goto error;
    }
    
    if (fread(rootSectorBuffer, _XBIFSRootSectorSize, 1, newImage->file) != 1) {
        *error = kXBIFileIOError;
        goto error;
    }
    
    rootSectorBlockOffset = XBIReadLittleEndianInt32AtAddress(rootSectorBuffer + 0x14);
    rootSectorSize = XBIReadLittleEndianInt32AtAddress(rootSectorBuffer + 0x18);
    
    XBILog("Root sector block is %d sectors after FS offset\n", rootSectorBlockOffset);
    XBILog("Root sector size is %d\n", rootSectorSize);
    
    newImage->fileSystemRootOffest = (off_t)rootSectorBlockOffset * kXBIDVDSectorSize;
    XBILog("Root sector block is at byte 0x%qX\n", newImage->dataPartitionOffset + newImage->fileSystemRootOffest);

    XBIAssert(rootSectorSize % 2048 == 0, "Root sector size should be a multiple of 2048");
/*    // rootSectorSize should be 2048 byte aligned anyhow, but check to make sure
    if ((rootSectorSize % 2048) != 0)
        rootSectorSize += (2048 - (rootSectorSize % 2048));
 */
    
    newImage->fileSystemRoot = XBIXFSElementCreate(newImage, error);
    
    free(rootSectorBuffer);
    rootSectorBuffer = NULL;
    
    
    XBILog("Reading Video header\n");

    videoBuffer = malloc(_XBIVideoHeaderSize);
    
    if (videoBuffer == NULL) {
        *error = kXBINotEnoughMemory;
        goto error;    
    }
    
    if (fseeko(newImage->file, 0x8000, SEEK_SET) != 0) {
        *error = kXBIFileIOError;
        goto error;
    }
    
    if (fread(videoBuffer, _XBIVideoHeaderSize, 1, newImage->file) != 1) {
        *error = kXBIFileIOError;
        goto error;
    }
    
    if (memcmp(videoBuffer, _XBIVideoHeaderMagicCookie, _XBIVideoHeaderMagicCookieLength) == 0) {
        XBILog("CD001 Video header exists\n");
        
        videoSizeInSectors = XBIReadLittleEndianInt32AtAddress(videoBuffer + 0x50);
        XBILog("Video size in sectors : %d\n", videoSizeInSectors);
        if (videoSizeInSectors > 3 && videoSizeInSectors <= 50000) {
            XBILog("Size is appropriate, marking video as present (but unchecked for validity)\n");
            newImage->hasVideoL0 = 1;
        } else {
            newImage->hasVideoL0 = 0;
        }
        
        
    }
    
    if (newImage->PFI != NULL) {
        
        if (newImage->hasVideoL0) {
            if (fseeko(newImage->file, XBIPFIGetLayer0SizeInSectors(newImage->PFI) * kXBIDVDSectorSize, SEEK_SET) != 0) {
                *error = kXBIFileIOError;
                goto error;
            }
            
            if (fread(videoBuffer, _XBIVideoHeaderSize, 1, newImage->file) != 1) {
                *error = kXBIFileIOError;
                goto error;
            }
            
            if (XBIBufferIsEmpty(videoBuffer, _XBIVideoHeaderSize)) {
                newImage->hasVideoL1OnL0 = 0;
                XBILog("Video L1 on L0 area appears to be empty\n");
            } else {
                newImage->hasVideoL1OnL0 = 1;
                XBILog("Found data in video L1 on L0 area\n");
            }
            
            if (newImage->SS != NULL) {
                if (XBIImageHasSplitVidPartition(newImage)) {
                    
                    XBIAssert(_XBIImageGetVidL1Position(newImage) < newImage->fileSize, "ISO is too short to have a splitvid partition");
                    
                    if (fseeko(newImage->file, _XBIImageGetVidL1Position(newImage), SEEK_SET) != 0) {
                        *error = kXBIFileIOError;
                        goto error;
                    }
                    
                    if (fread(videoBuffer, _XBIVideoHeaderSize, 1, newImage->file) != 1) {
                        *error = kXBIFileIOError;
                        goto error;
                    }
                    
                    if (XBIBufferIsEmpty(videoBuffer, _XBIVideoHeaderSize)) {
                        newImage->hasVideoL1OnL1 = 0;
                        XBILog("Video L1 on L1 area appears to be empty\n");
                    } else {
                        newImage->hasVideoL1OnL1 = 1;
                        XBILog("Found data in video L1 on L1 area\n");
                    }
                    
                } else {
                    XBILog("Source file is too small to contain a video L1 partition\n");
                }
            }
            
        } else {
            XBILog("No video data in ISO\n");
        }
        
    } else {
        XBILog("No PFI data in ISO\n");
    }
    
    free(videoBuffer);
    videoBuffer = NULL;
    

    if (newImage->hasVideoL0) {
        // Fill videoL0Buffer
        videoL0Buffer = malloc(_XBIImageGetVidL0Size(newImage));
        if (videoL0Buffer == NULL) {
            *error = kXBINotEnoughMemory;
            goto error;
        }                    
        if (fseeko(newImage->file, _XBIImageGetVidL0Position(newImage), SEEK_SET) != 0) {
            *error = kXBIFileIOError;
            goto error;
        }                    
        if (fread(videoL0Buffer, _XBIImageGetVidL0Size(newImage), 1, newImage->file) != 1) {
            *error = kXBIFileIOError;
            goto error;
        }
        
        newImage->firstIVH = XBIIVHCreateFromBuffer(videoL0Buffer, _XBIImageGetVidL0Size(newImage), error);
        
        if (videoL0Buffer) {
            free(videoL0Buffer);
            videoL0Buffer = NULL;
        }
        
        XBILog("Vid Layer0 CRC : 0x%X\n", XBIIVHGetCRC32(newImage->firstIVH));
    }
    
    if (newImage->hasVideoL1OnL1 || newImage->hasVideoL1OnL0) {
        // Fill videoL1Buffer
        videoL1Buffer = malloc(_XBIImageGetVidL1Size(newImage));
        if (videoL1Buffer == NULL) {
            *error = kXBINotEnoughMemory;
            goto error;
        }
        if (newImage->hasVideoL1OnL1) { // If iso is SplitVid, then the second half of the video in on L1
            XBILog("Iso is splitVid (second half of intro video on L1), reading from there\n");
            if (fseeko(newImage->file, _XBIImageGetVidL1Position(newImage), SEEK_SET) != 0) {
                *error = kXBIFileIOError;
                goto error;
            }     
        } else if (newImage->hasVideoL1OnL0) { // If iso has videoL1onL0, then the second half of the video is on L0, right after the first half
            XBILog("Iso is NOT spliVid (second half of intro video on L0, right after first half), reading from there\n");
            if (fseeko(newImage->file, _XBIImageGetVidL0Position(newImage) + _XBIImageGetVidL0Size(newImage), SEEK_SET) != 0) {
                *error = kXBIFileIOError;
                goto error;
            }     
        }
        
        if (fread(videoL1Buffer, _XBIImageGetVidL1Size(newImage), 1, newImage->file) != 1) {
            *error = kXBIFileIOError;
            goto error;
        }
        
        newImage->secondIVH = XBIIVHCreateFromBuffer(videoL1Buffer, _XBIImageGetVidL1Size(newImage), error);

        if (videoL1Buffer) {
            free(videoL1Buffer);
            videoL1Buffer = NULL;
        }
        
        XBILog("Vid Layer1 CRC : 0x%X\n", XBIIVHGetCRC32(newImage->secondIVH));
    }
    
    return newImage;
    
error:
    if (newImage == NULL || newImage->dataPartitionOffset == 0) // Keep the image as long as dataPartitionOffset is not 0
        XBIImageDelete(newImage);
    
    if (magicCookieBuffer != NULL) {
        free(magicCookieBuffer);
        magicCookieBuffer = NULL;
    }
    if (stealthBuffer != NULL) {
        free(stealthBuffer);
        stealthBuffer = NULL;
    }
    if (videoBuffer != NULL) {
        free(videoBuffer);
        videoBuffer = NULL;
    }
    if (videoL0Buffer) {
        free(videoL0Buffer);
        videoL0Buffer = NULL;
    }
    if (videoL1Buffer) {
        free(videoL1Buffer);
        videoL1Buffer = NULL;
    }
    return NULL;
}

void XBIImageDelete(XBIImage * image) {
    if (image != NULL) {
        XBISSDelete(image->SS);
        XBIPFIDelete(image->PFI);
        XBIDMIDelete(image->DMI);
        XBIXEXDelete(image->defaultXEX);
        XBIXFSElementDelete(image->fileSystemRoot);
        
        free(image);
    }
}

#if 0
#pragma mark Data manipulation
#endif

int XBIImageHasSplitVidPartition(XBIImage * image) {
    if (image == NULL)
        return 0;
    XBILog("Size for SplitVid = %lld\n", _XBIImageISOSizeWithSplitVid(image));
    return (image->fileSize == _XBIImageISOSizeWithSplitVid(image));
}

int XBIImageIsXbox(XBIImage * image);
int XBIImageIsXbox360(XBIImage * image);

int XBIImageIsStealthPatched(XBIImage * image) {
    return _XBIImageStealthCheckLevel1(image) && _XBIImageStealthCheckLevel2(image);
}

size_t XBIImageCopyDataForXFSElement(XBIImage * image, byte_t * buffer, size_t bufferSize, XBIXFSElement * element, XBIError * error) {
    size_t lengthToRead = 0;
    
    if (buffer == NULL || bufferSize <= 0 || element == NULL)
        goto error;
 
    lengthToRead = XBIXFSElementGetSize(element) < bufferSize ? XBIXFSElementGetSize(element) : bufferSize;
    
    if (fseeko(image->file, XBIImageGetDataPartitionOffset(image) + XBIXFSElementGetStartByte(element), SEEK_SET) != 0) {
        *error = kXBIFileIOError;
        goto error;
    }
    
    if (fread(buffer, lengthToRead, 1, image->file) != 1) {
        *error = kXBIFileIOError;
        goto error;
    }
    
    return lengthToRead;
error:
    return -1;
}

int XBIImageWriteXFSElementToFile(XBIImage * image, XBIXFSElement * element, FILE * file, XBIError * error) {
    uint64_t lengthToCopy = 0;
    byte_t * copyBuffer = NULL;
    size_t   bufferSize = 0;
    
    if (image == NULL || element == NULL || file == NULL)
        goto error;
    
    if (XBIXFSElementIsFolder(element)) {
        XBILog("Writing whole folders to disk isn't supported\n");
        goto error;
    }
    
    copyBuffer = malloc(_XBIFileCopyBufferSize);    
    if (copyBuffer == NULL) {
        *error = kXBINotEnoughMemory;
        goto error;
    }
    
    lengthToCopy = XBIXFSElementGetSize(element);
    
    while (lengthToCopy > 0) {
        
        bufferSize =  lengthToCopy < _XBIFileCopyBufferSize ? lengthToCopy : _XBIFileCopyBufferSize;
    
        if (fseeko(image->file, XBIImageGetDataPartitionOffset(image) + XBIXFSElementGetStartByte(element) + (XBIXFSElementGetSize(element) - lengthToCopy), SEEK_SET) != 0) {
            *error = kXBIFileIOError;
            goto error;
        }
    
        if (fread(copyBuffer, bufferSize, 1, image->file) != 1) {
            *error = kXBIFileIOError;
            goto error;
        }
    
        if (fwrite(copyBuffer, bufferSize, 1, file) != 1) {
            *error = kXBIFileIOError;
            goto error;
        }
        
        lengthToCopy -= bufferSize;
    }
    
    free(copyBuffer);
    return 0;
error:
    if (copyBuffer != NULL) {
        free(copyBuffer);
        copyBuffer = NULL;
    }
    return -1;    
}


XBIXboxEXecutable * XBIImageGetDefaultXEX(XBIImage * image, XBIError * error) {
    // Lazy loading of default.xex

    byte_t * fileBuffer = NULL;
    size_t fileBufferSize = 0;
    XBIXFSElement * defaultXEXElement = NULL;
    
    if (image == NULL)
        return NULL;
    
    if (image->defaultXEX != NULL)
        return image->defaultXEX;
    
    if (image->fileSystemRoot == NULL)
        goto error;
    
    defaultXEXElement = XBIXFSElementFindChildWithPath(image->fileSystemRoot, _XBIXboxDefaultExecutablePath, _XBIXboxDefaultExecutablePathLength);
        
    if (defaultXEXElement == NULL)
        goto error;
    
    fileBufferSize = XBIXFSElementGetSize(defaultXEXElement);
    
    fileBuffer = malloc(fileBufferSize);
    if (fileBuffer == NULL) {
        *error = kXBINotEnoughMemory;
        goto error;
    }
    
    if (XBIImageCopyDataForXFSElement(image, fileBuffer, fileBufferSize, defaultXEXElement, error) != fileBufferSize)
        goto error;

    image->defaultXEX = XBIXEXCreateFromBuffer(fileBuffer, fileBufferSize, error);

    free(fileBuffer);
    fileBuffer = NULL;
    
    return image->defaultXEX;
error:
    return NULL;
}


#if 0
#pragma mark Accessors
#endif

XBISecuritySector * XBIImageGetSS(XBIImage * image) {
    if (image != NULL)
        return image->SS;
    return NULL;    
}

XBIPhysicalFormatInformation * XBIImageGetPFI(XBIImage * image) {
    if (image != NULL)
        return image->PFI;
    return NULL;    
}

XBIDiskManufacturingInformation * XBIImageGetDMI(XBIImage * image) {
    if (image != NULL)
        return image->DMI;
    return NULL;    
}

int XBIImageSetSS(XBIImage * image, XBISecuritySector * ss, XBIError * error) {
    if (image == NULL)
        goto error;
    
    if (image->dataPartitionOffset == 0) {
        XBILog("Can't set SS without data partition offset\n");
        *error = kXBIFileWrongFormat;
        goto error;
    }
    
    XBISSDelete(image->SS);
    image->SS = XBISSCopy(ss);    
    return _XBIImageWriteBufferAtOffset(image, image->dataPartitionOffset - _XBISecuritySectorSize, XBISSGetData(ss), XBISSGetSize(ss), error);
error:
    return -1;    
}

int XBIImageSetDMI(XBIImage * image, XBIDiskManufacturingInformation * dmi, XBIError * error) {
    if (image == NULL)
        goto error;

    if (image->dataPartitionOffset == 0) {
        XBILog("Can't set DMI without data partition offset\n");
        *error = kXBIFileWrongFormat;
        goto error;
    }    
    
    XBIDMIDelete(image->DMI);
    image->DMI = XBIDMICopy(dmi);    
    return _XBIImageWriteBufferAtOffset(image, image->dataPartitionOffset - (_XBISecuritySectorSize + _XBIDiskManufacturingInformationSize), XBIDMIGetData(dmi), XBIDMIGetSize(dmi), error);
error:
    return -1;    
}

int XBIImageSetPFI(XBIImage * image, XBIPhysicalFormatInformation * pfi, XBIError * error) {
    if (image == NULL)
        goto error;
    
    if (image->dataPartitionOffset == 0) {
        XBILog("Can't set PFI without data partition offset\n");
        *error = kXBIFileWrongFormat;
        goto error;
    }    
    
    XBIPFIDelete(image->PFI);
    image->PFI = XBIPFICopy(pfi);    
    return _XBIImageWriteBufferAtOffset(image, image->dataPartitionOffset - (_XBISecuritySectorSize + _XBIPhysicalFormatInformationSize + _XBIDiskManufacturingInformationSize), XBIPFIGetData(pfi), XBIPFIGetSize(pfi), error);
error:
    return -1;    
}

FILE * XBIImageGetFilePointer(XBIImage * image) {
    if (image != NULL)
        return image->file;
    return NULL;
}

off_t XBIImageGetFileSize(XBIImage * image) {
    if (image != NULL)
        return image->fileSize;
    return -1;    
}

off_t XBIImageGetFileSystemRootOffset(XBIImage * image) {
    if (image != NULL)
        return image->fileSystemRootOffest;
    return -1;
}

off_t XBIImageGetDataPartitionOffset(XBIImage * image) {
    if (image != NULL)
        return image->dataPartitionOffset;
    return -1;
}

XBIXFSElement * XBIImageGetRootFileSystemElement(XBIImage * image) {
    if (image != NULL)
        return image->fileSystemRoot;
    return NULL;    
}

XBIIntroVideoHalf * XBIImageGetFirstIVH(XBIImage * image) {
    if (image != NULL)
        return image->firstIVH;
    return NULL;    
}

XBIIntroVideoHalf * XBIImageGetSecondIVH(XBIImage * image) {
    if (image != NULL)
        return image->secondIVH;
    return NULL;
}

int XBIImageSetFirstIVH(XBIImage * image, XBIIntroVideoHalf * ivh, XBIError * error) {
    if (image == NULL)
        goto error;
    
    XBIIVHDelete(image->firstIVH);
    image->firstIVH = XBIIVHCopy(ivh);
    XBIAssert(XBIIVHGetSize(ivh) == _XBIImageGetVidL0Size(image), "Blitting first IVH video buffer with wrong size\n");
    return _XBIImageWriteBufferAtOffset(image, _XBIImageGetVidL0Position(image), XBIIVHGetData(ivh), XBIIVHGetSize(ivh), error);
error:
    return -1;
}

int XBIImageSetSecondIVH(XBIImage * image, XBIIntroVideoHalf * ivh, XBIError * error) {
    if (image == NULL)
        goto error;
    
    XBIIVHDelete(image->secondIVH);
    image->secondIVH = XBIIVHCopy(ivh);
    XBIAssert(XBIIVHGetSize(ivh) == _XBIImageGetVidL1Size(image), "Blitting second IVH video buffer with wrong size\n");
    if (XBIImageHasSplitVidPartition(image)) { // ISO has room for video on L1, lets' blit here
        XBILog("ISO has room for video on Layer 1, blitting second half of video there (therefore creating a splitVid iso)\n");
        return _XBIImageWriteBufferAtOffset(image, _XBIImageGetVidL1Position(image), XBIIVHGetData(ivh), XBIIVHGetSize(ivh), error);
    } else {
        XBILog("ISO has NO room for video on Layer 1. Blitting second half of video on L0\n");
        return _XBIImageWriteBufferAtOffset(image, _XBIImageGetVidL0Position(image) + _XBIImageGetVidL0Size(image), XBIIVHGetData(ivh), XBIIVHGetSize(ivh), error);        
    }
error:
    return -1;    
}

